﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using Notoric.Model.Collections;
using Notoric.Model.Internal;
using Notoric.Model.Packaging;
using System.ComponentModel;

namespace Notoric.Model.Elements.CWNotation
{
    /// <summary>
    /// Represents a system of staves.
    /// </summary>
    public class System : ModelElement, IResizableElement, IPositionableElement
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="System"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part that owns this system.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="MusicSectionInterior"/>
        /// </para>
        /// <para>
        /// The music section interior element that directly owns this system.
        /// </para>
        /// </param>
        /// <param name="firstMeasure">
        /// <para>
        /// Type: <see cref="MeasureElement"/>
        /// </para>
        /// <para>
        /// The first measure of this system.
        /// </para>
        /// </param>
        /// <param name="lastMeasure">
        /// <para>
        /// Type: <see cref="MeasureElement"/>
        /// </para>
        /// <para>
        /// The last measure of this system.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The <paramref name="lastMeasure"/> has a higher number than the <paramref name="firstMeasure"/>.
        /// </exception>
        internal System(XmlPackagePart packagePart, MusicSectionInterior owner, MeasureElement firstMeasure, MeasureElement lastMeasure)
            : base(packagePart, owner)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");
            if (firstMeasure == null)
                throw new ArgumentNullException("firstMeasure");
            if (lastMeasure == null)
                throw new ArgumentNullException("lastMeasure");
            if (firstMeasure.Number > lastMeasure.Number)
                throw new ArgumentException("The lastMeasure has lower number than the firstMeasure!");

            barlinesCollection = new ElementCollection<Barline>(this, barlinesElement);
            barlines = new ReadOnlyCollection<Barline>(barlinesCollection);

            connectorsCollection = new ElementCollection<Connector>(this, connectorsElement);
            connectors = new ReadOnlyCollection<Connector>(connectorsCollection);

            measuresCollection = new ObservableCollection<MeasureElement>();
            measures = new ReadOnlyCollection<MeasureElement>(measuresCollection);
            measuresCollection.CollectionChanged += new EventHandler<NotifyCollectionChangedEventArgs>(measuresCollection_CollectionChanged);

            stavesCollection = new ElementCollection<Staff>(this, stavesElement);
            staves = new ReadOnlyCollection<Staff>(stavesCollection);

            staffConnectorsCollection = new ElementCollection<StaffConnector>(this, staffConnectorsElement);

            SetMeasures(firstMeasure, lastMeasure);
            CreateStaves();

            var section = FindOwner<MusicSectionElement>();

            foreach (ConnectorElement connector in section.Composition.Composition.Connectors)
            {
                try
                {
                    connectorsCollection.Add(Connector.CreateRepresentation(packagePart, this, connector));
                }
                catch (ArgumentException)
                {
                    // The connector does not belong to this system
                }
            }

            section.Parts.CollectionChanged += new EventHandler<NotifyCollectionChangedEventArgs>(Parts_CollectionChanged);
            section.Composition.Composition.Connectors.CollectionChanged += new EventHandler<NotifyCollectionChangedEventArgs>(Connectors_CollectionChanged);
        }

        /// <summary>
        /// Creates a new <see cref="System"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part that owns this system.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="MusicSectionInterior"/>
        /// </para>
        /// <para>
        /// The music section interior element that directly owns this system.
        /// </para>
        /// </param>
        /// <param name="element">
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The XML element that holds the data of this object.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        internal System(XmlPackagePart packagePart, MusicSectionInterior owner, XmlElement element)
            : base(packagePart, owner, element)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");

            barlinesElement = element[PackageVocabulary.BarlineElementName + "s", PackageVocabulary.PackageNamespaceUri];
            barlinesCollection = new ElementCollection<Barline>(this, barlinesElement);
            barlines = new ReadOnlyCollection<Barline>(barlinesCollection);

            connectorsElement = element[PackageVocabulary.ConnectorElementName + "s", PackageVocabulary.PackageNamespaceUri];
            connectorsCollection = new ElementCollection<Connector>(this, connectorsElement);
            connectors = new ReadOnlyCollection<Connector>(connectorsCollection);

            measuresCollection = new ObservableCollection<MeasureElement>();
            measures = new ReadOnlyCollection<MeasureElement>(measuresCollection);
            measuresCollection.CollectionChanged += new EventHandler<NotifyCollectionChangedEventArgs>(measuresCollection_CollectionChanged);
            for (int i = 0; i < barlinesCollection.Count - 1; ++i)
                measuresCollection.Add(barlinesCollection[i].Right);

            stavesElement = element[PackageVocabulary.StavesElementName, PackageVocabulary.PackageNamespaceUri];
            stavesCollection = new ElementCollection<Staff>(this, stavesElement);
            staves = new ReadOnlyCollection<Staff>(stavesCollection);

            staffConnectorsElement = element[PackageVocabulary.StaffConnectorElementName + "s", PackageVocabulary.PackageNamespaceUri];
            staffConnectorsCollection = new ElementCollection<StaffConnector>(this, staffConnectorsElement);

            positionableHelper = PositionableHelper.Load(element);
            resizableHelper = ResizableHelper.Load(element);

            var section = FindOwner<MusicSectionElement>();
            section.Parts.CollectionChanged += new EventHandler<NotifyCollectionChangedEventArgs>(Parts_CollectionChanged);
            section.Composition.Composition.Connectors.CollectionChanged += new EventHandler<NotifyCollectionChangedEventArgs>(Connectors_CollectionChanged);
        }

        #endregion

        #region ModelElement Members

        /// <inheritdoc/>
        protected override XmlElement CreateObjectElement(XmlDocument document)
        {
            var element = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.SystemElementName,
                PackageVocabulary.PackageNamespaceUri
                );

            barlinesElement = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.BarlineElementName + "s",
                PackageVocabulary.PackageNamespaceUri
                );

            connectorsElement = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.ConnectorElementName + "s",
                PackageVocabulary.PackageNamespaceUri
                );

            staffConnectorsElement = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.StaffConnectorElementName + "s",
                PackageVocabulary.PackageNamespaceUri
                );

            stavesElement = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.StavesElementName,
                PackageVocabulary.PackageNamespaceUri
                );

            element.AppendChild(barlinesElement);
            element.AppendChild(connectorsElement);
            element.AppendChild(stavesElement);
            element.AppendChild(staffConnectorsElement);

            positionableHelper = PositionableHelper.Create(element);
            resizableHelper = ResizableHelper.Create(element);

            return element;
        }

        #endregion

        #region IPositionableElement Members

        /// <inheritdoc/>
        public double Left
        {
            get
            {
                return positionableHelper.Left;
            }
            set
            {
                positionableHelper.Left = value;
                RaisePropertyChanged("Left");
            }
        }

        /// <inheritdoc/>
        public double Top
        {
            get
            {
                return positionableHelper.Top;
            }
            set
            {
                positionableHelper.Top = value;
                RaisePropertyChanged("Top");
            }
        }

        #endregion

        #region IResizableElement Members

        /// <inheritdoc/>
        public double Height
        {
            get
            {
                return resizableHelper.Height;
            }
            set
            {
                ValidateAndThrow("Height", value);

                resizableHelper.Height = value;
                RaisePropertyChanged("Height");
            }
        }

        /// <inheritdoc/>
        public double Width
        {
            get
            {
                return resizableHelper.Width;
            }
            set
            {
                ValidateAndThrow("Width", value);

                resizableHelper.Width = value;
                RaisePropertyChanged("Width");
            }
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Occurs when the <see cref="CompositionElement.Connectors"/> collection of the presented <see cref="CompositionElement"/> has changed.
        /// </summary>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void Connectors_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    foreach (ConnectorElement connector in e.NewItems)
                    {
                        try
                        {
                            connectorsCollection.Add(Connector.CreateRepresentation(PackagePart, this, connector));
                        }
                        catch (ArgumentException)
                        {
                            // The connector does not belong to this system.
                        }
                    }
                    break;
                case NotifyCollectionChangedAction.Remove:
                    foreach (ConnectorElement connector in e.OldItems)
                    {
                        var index = connectorsCollection.FindIndex(c => c.PresentedConnector == connector);
                        if (index != -1)
                            connectorsCollection.RemoveAt(index);
                    }
                    break;
                case NotifyCollectionChangedAction.Replace:
                    foreach (ConnectorElement connector in e.OldItems)
                    {
                        var index = connectorsCollection.FindIndex(c => c.PresentedConnector == connector);
                        if (index != -1)
                            connectorsCollection.RemoveAt(index);
                    }
                    foreach (ConnectorElement connector in e.NewItems)
                    {
                        try
                        {
                            connectorsCollection.Add(Connector.CreateRepresentation(PackagePart, this, connector));
                        }
                        catch (ArgumentException)
                        {
                            // The connector does not belong to this system.
                        }
                    }
                    break;
                case NotifyCollectionChangedAction.Reset:
                    connectorsCollection.Clear();
                    break;
            }
        }

        /// <summary>
        /// Occurs when the <see cref="Measures"/> collection has changed.
        /// </summary>
        /// <remarks>
        /// <b>Operation:</b><br/>
        /// If a measure is added, the handler binds to the <see cref="ObservableCollection{T}.CollectionChanged"/> event
        /// of its <see cref="MeasureElement.Parts"/> collection.<br/>
        /// If a measure is removed, the handler unbinds from the <see cref="ObservableCollection{T}.CollectionChanged"/> event
        /// of its <see cref="MeasureElement.Parts"/> collection.
        /// </remarks>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void measuresCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    foreach (MeasureElement measure in e.NewItems)
                        BindMeasure(measure);
                    break;
                case NotifyCollectionChangedAction.Remove:
                    foreach (MeasureElement measure in e.OldItems)
                        UnbindMeasure(measure);
                    break;
            }
        }

        /// <summary>
        /// Occurs when the parts collection of some of the presented measures has changed.
        /// </summary>
        /// <remarks>
        /// <b>Operation:</b><br/>
        /// If a part was added, the handler binds the staves that present it to the <see cref="MusicalContentElement.MusicalContentChanged"/>
        /// event of its <see cref="PartElement.Content"/>.
        /// If a part was removed, it unbinds the staves from the above event.
        /// </remarks>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void Measure_Parts_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                {
                    foreach (PartElement part in e.NewItems)
                    {
                        var partStaves = from s in stavesCollection
                                         where s.Descriptor.FindOwner<PartDescriptorElement>() == part.Descriptor
                                         select s;

                        foreach (Staff staff in partStaves)
                            part.Content.MusicalContentChanged += new EventHandler<MusicalContentChangedEventArgs>(staff.Part_MusicalContentChanged);
                    }
                    break;
                }
                case NotifyCollectionChangedAction.Remove:
                {
                    foreach (PartElement part in e.OldItems)
                    {
                        var partStaves = from s in stavesCollection
                                         where s.Descriptor.FindOwner<PartDescriptorElement>() == part.Descriptor
                                         select s;

                        foreach (Staff staff in partStaves)
                            part.Content.MusicalContentChanged -= staff.Part_MusicalContentChanged;
                    }
                    break;
                }
            }
        }

        /// <summary>
        /// Occurs when the collection of presented parts of the owner <see cref="MusicSectionElement"/> has changed.
        /// </summary>
        /// <remarks>
        /// <b>Operation:</b><br/>
        /// The handler modifies the <see cref="Staves"/> collection so that it contains just the staves of the presented parts
        /// and that they are in the correct order.
        /// </remarks>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void Parts_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                {
                    if (stavesCollection.Count == 0)
                        CreateStaves();
                    else
                    {
                        int partIndex = 0, staffIndex = 0;
                        var lastPart = stavesCollection[0].Descriptor.FindOwner<PartDescriptorElement>();
                        while (partIndex < e.NewStartingIndex && staffIndex < stavesCollection.Count)
                        {
                            var currentPart = stavesCollection[staffIndex].Descriptor.FindOwner<PartDescriptorElement>();
                            if (currentPart != lastPart)
                            {
                                lastPart = currentPart;
                                ++partIndex;
                            }
                            ++staffIndex;
                        }

                        foreach (PartDescriptorElement pref in e.NewItems)
                        {
                            var cwPartData = pref.FindCustomData<CWPartData>();

                            foreach (StaffDescriptor descriptor in cwPartData.Staves)
                            {
                                var staff = new Staff(PackagePart, this, descriptor);
                                stavesCollection.Insert(staffIndex++, staff);

                                foreach (MeasureElement measure in measuresCollection)
                                {
                                    var part = measure.Parts.FirstOrDefault(p => p.Descriptor == pref);
                                    if (part != null)
                                        part.Content.MusicalContentChanged += new EventHandler<MusicalContentChangedEventArgs>(staff.Part_MusicalContentChanged);
                                }
                            }

                            foreach (var connectorDescriptor in cwPartData.StaffConnectors)
                            {
                                var lowerStaff = stavesCollection.First(s => s.Descriptor == connectorDescriptor.LowerStaff);
                                var upperStaff = stavesCollection.First(s => s.Descriptor == connectorDescriptor.UpperStaff);

                                if (connectorDescriptor.ConnectorClass == typeof(Brace))
                                    staffConnectorsCollection.Add(new Brace(PackagePart, this, lowerStaff, upperStaff));
                                else if (connectorDescriptor.ConnectorClass == typeof(Bracket))
                                    staffConnectorsCollection.Add(new Bracket(PackagePart, this, lowerStaff, upperStaff));
                            }
                        }
                    }

                    foreach (ConnectorElement connector in FindOwner<MusicSectionElement>().Composition.Composition.Connectors)
                    {
                        try
                        {
                            if (!connectorsCollection.Any(c => c.PresentedConnector == connector))
                                connectorsCollection.Add(Connector.CreateRepresentation(PackagePart, this, connector));
                        }
                        catch (ArgumentException)
                        {
                            // The connector does not belong to this system
                        }
                    }
                    break;
                }
                case NotifyCollectionChangedAction.Remove:
                {
                    foreach (PartDescriptorElement part in e.OldItems)
                    {
                        var toRemove = new List<Staff>(from staff in stavesCollection
                                                        where staff.Descriptor.FindOwner<PartDescriptorElement>() == part
                                                        select staff);
                        foreach (Staff staff in toRemove)
                            stavesCollection.Remove(staff);

                        var removedStaffConnectors = new List<StaffConnector>(from connector in staffConnectorsCollection
                                                                              where connector.LowerStaff.Descriptor.FindOwner<PartDescriptorElement>() == part
                                                                              select connector);

                        foreach (var connector in removedStaffConnectors)
                            staffConnectorsCollection.Remove(connector);

                        var removedConnectors = new List<Connector>(from connector in connectorsCollection
                                                                    where (connector.Start != null && connector.Start.FindOwner<PartElement>().Descriptor == part) ||
                                                                          (connector.End != null && connector.End.FindOwner<PartElement>().Descriptor == part)
                                                                    select connector);

                        foreach (Connector connector in removedConnectors)
                            connectorsCollection.Remove(connector);
                    }
                    break;
                }
                case NotifyCollectionChangedAction.Move:
                {
                    throw new NotImplementedException();
                }
                case NotifyCollectionChangedAction.Replace:
                {
                    foreach (PartDescriptorElement part in e.OldItems)
                    {
                        var toRemove = new List<Staff>(from staff in stavesCollection
                                                       where staff.Descriptor.FindOwner<PartDescriptorElement>() == part
                                                       select staff);
                        foreach (Staff staff in toRemove)
                            stavesCollection.Remove(staff);

                        var removedStaffConnectors = new List<StaffConnector>(from connector in staffConnectorsCollection
                                                                              where connector.LowerStaff.Descriptor.FindOwner<PartDescriptorElement>() == part
                                                                              select connector);

                        foreach (var connector in removedStaffConnectors)
                            staffConnectorsCollection.Remove(connector);

                        var removedConnectors = new List<Connector>(from connector in connectorsCollection
                                                                    where (connector.Start != null && connector.Start.FindOwner<PartElement>().Descriptor == part) ||
                                                                          (connector.End != null && connector.End.FindOwner<PartElement>().Descriptor == part)
                                                                    select connector);

                        foreach (Connector connector in removedConnectors)
                            connectorsCollection.Remove(connector);
                    }
                    if (stavesCollection.Count == 0)
                        CreateStaves();
                    else
                    {
                        int partIndex = 0, staffIndex = 0;
                        var lastPart = stavesCollection[0].Descriptor.FindOwner<PartDescriptorElement>();
                        while (partIndex < e.NewStartingIndex)
                        {
                            var currentPart = stavesCollection[staffIndex].Descriptor.FindOwner<PartDescriptorElement>();
                            if (currentPart != lastPart)
                            {
                                lastPart = currentPart;
                                ++partIndex;
                            }
                            ++staffIndex;
                        }

                        foreach (PartDescriptorElement pref in e.NewItems)
                        {
                            var cwPartData = pref.FindCustomData<CWPartData>();

                            foreach (StaffDescriptor descriptor in cwPartData.Staves)
                            {
                                var staff = new Staff(PackagePart, this, descriptor);
                                stavesCollection.Insert(staffIndex++, staff);

                                foreach (MeasureElement measure in measuresCollection)
                                {
                                    var part = measure.Parts.FirstOrDefault(p => p.Descriptor == pref);
                                    if (part != null)
                                        part.Content.MusicalContentChanged += new EventHandler<MusicalContentChangedEventArgs>(staff.Part_MusicalContentChanged);
                                }
                            }

                            foreach (var connectorDescriptor in cwPartData.StaffConnectors)
                            {
                                var lowerStaff = stavesCollection.First(s => s.Descriptor == connectorDescriptor.LowerStaff);
                                var upperStaff = stavesCollection.First(s => s.Descriptor == connectorDescriptor.UpperStaff);

                                if (connectorDescriptor.ConnectorClass == typeof(Brace))
                                    staffConnectorsCollection.Add(new Brace(PackagePart, this, lowerStaff, upperStaff));
                                else if (connectorDescriptor.ConnectorClass == typeof(Bracket))
                                    staffConnectorsCollection.Add(new Bracket(PackagePart, this, lowerStaff, upperStaff));
                            }
                        }
                    }

                    foreach (ConnectorElement connector in FindOwner<MusicSectionElement>().Composition.Composition.Connectors)
                    {
                        try
                        {
                            if (!connectorsCollection.Any(c => c.PresentedConnector == connector))
                                connectorsCollection.Add(Connector.CreateRepresentation(PackagePart, this, connector));
                        }
                        catch (ArgumentException)
                        {
                            // The connector does not belong to this system
                        }
                    }
                    break;
                }
                case NotifyCollectionChangedAction.Reset:
                    stavesCollection.Clear();
                    break;
            }

            FindOwner<MusicSectionInterior>().Layout();
        }

        /// <summary>
        /// Occurs when a property of a presented measure has changed.
        /// </summary>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data.</param>
        private void measure_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Meter")
            {
                foreach (var staff in stavesCollection)
                    staff.PresentTimeSignatures();
            }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Binds the staves to the <see cref="MusicalContentElement.MusicalContentChanged"/> event
        /// of the parts that exists in a given <paramref name="measure"/> and this system
        /// to the <see cref="ObservableCollection{T}.CollectionChanged"/> event 
        /// of its <see cref="MeasureElement.Parts"/> collection.
        /// </summary>
        /// <param name="measure">Measure being bound</param>
        private void BindMeasure(MeasureElement measure)
        {
            if (stavesCollection != null)
            {
                foreach (PartElement part in measure.Parts)
                {
                    var partStaves = from s in stavesCollection
                                     where s.Descriptor.FindOwner<PartDescriptorElement>() == part.Descriptor
                                     select s;

                    foreach (Staff staff in partStaves)
                        part.Content.MusicalContentChanged += new EventHandler<MusicalContentChangedEventArgs>(staff.Part_MusicalContentChanged);
                }
            
                foreach (ConnectorElement connector in measure.FindOwner<CompositionElement>().Connectors)
                {
                    if (!connectorsCollection.Any(c => c.PresentedConnector == connector))
                    {
                        try
                        {
                            connectorsCollection.Add(Connector.CreateRepresentation(PackagePart, this, connector));
                        }
                        catch (ArgumentException)
                        {
                            // The connector does not belong to this system.
                        }
                    }
                }

                foreach (var staff in stavesCollection)
                    staff.PresentTimeSignatures();
            }

            measure.Parts.CollectionChanged += new EventHandler<NotifyCollectionChangedEventArgs>(Measure_Parts_CollectionChanged);
            measure.PropertyChanged += new PropertyChangedEventHandler(measure_PropertyChanged);
        }

        /// <summary>
        /// Creates all the presented staves.
        /// </summary>
        private void CreateStaves()
        {
            foreach (PartDescriptorElement pref in FindOwner<MusicSectionElement>().Parts)
            {
                var cwData = pref.FindCustomData<CWPartData>();

                foreach (StaffDescriptor descriptor in cwData.Staves)
                {
                    var staff = new Staff(PackagePart, this, descriptor);
                    stavesCollection.Add(staff);

                    foreach (MeasureElement measure in measuresCollection)
                    {
                        var part = measure.Parts.FirstOrDefault(p => p.Descriptor == pref);
                        if (part != null)
                            part.Content.MusicalContentChanged += new EventHandler<MusicalContentChangedEventArgs>(staff.Part_MusicalContentChanged);
                    }
                }

                foreach (var connectorDescriptor in cwData.StaffConnectors)
                {
                    var lowerStaff = stavesCollection.First(s => s.Descriptor == connectorDescriptor.LowerStaff);
                    var upperStaff = stavesCollection.First(s => s.Descriptor == connectorDescriptor.UpperStaff);

                    if (connectorDescriptor.ConnectorClass == typeof(Brace))
                        staffConnectorsCollection.Add(new Brace(PackagePart, this, lowerStaff, upperStaff));
                    else if (connectorDescriptor.ConnectorClass == typeof(Bracket))
                        staffConnectorsCollection.Add(new Bracket(PackagePart, this, lowerStaff, upperStaff));
                }
            }
        }

        /// <summary>
        /// Unbinds the staves from the <see cref="MusicalContentElement.MusicalContentChanged"/> event
        /// of the parts that exists in a given <paramref name="measure"/> and this system
        /// from the <see cref="ObservableCollection{T}.CollectionChanged"/> event 
        /// of its <see cref="MeasureElement.Parts"/> collection.
        /// </summary>
        /// <param name="measure">Measure being unbound</param>
        private void UnbindMeasure(MeasureElement measure)
        {
            if (stavesCollection != null)
            {
                foreach (PartElement part in measure.Parts)
                {
                    var partStaves = from s in stavesCollection
                                     where s.Descriptor.FindOwner<PartDescriptorElement>() == part.Descriptor
                                     select s;

                    foreach (Staff staff in partStaves)
                        part.Content.MusicalContentChanged -= new EventHandler<MusicalContentChangedEventArgs>(staff.Part_MusicalContentChanged);
                }

                foreach (var staff in stavesCollection)
                    staff.PresentTimeSignatures();
            }

            var removedConnectors = new List<Connector>(from connector in connectorsCollection
                                                        where (connector.Start != null && connector.Start.FindOwner<MeasureElement>() == measure) ||
                                                              (connector.End != null && connector.End.FindOwner<MeasureElement>() == measure)
                                                        select connector);
            foreach (var connector in removedConnectors)
                connectorsCollection.Remove(connector);

            measure.Parts.CollectionChanged -= new EventHandler<NotifyCollectionChangedEventArgs>(Measure_Parts_CollectionChanged);
            measure.PropertyChanged -= measure_PropertyChanged;
        }

        #endregion

        #region Internal Methods

        /// <summary>
        /// Creates a new <see cref="Barline"/> and adds it to the end of the barlines collection.
        /// </summary>
        /// <param name="left">
        /// <para>
        /// Type: <see cref="MeasureElement"/>
        /// </para>
        /// <para>
        /// The measure that is on the left side of the barline.
        /// This value may be <see langword="null"/> if the barline 
        /// is the first (also called systemic) barline of the system.
        /// </para>
        /// </param>
        /// <param name="right">
        /// <para>
        /// Type: <see cref="MeasureElement"/>
        /// </para>
        /// <para>
        /// The measure that is on the right side of the barline.
        /// This value may be <see langword="null"/> if the barline
        /// is the last barline of the system.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="Barline"/>
        /// </para>
        /// <para>
        /// The created barline.
        /// </para>
        /// </returns>
        internal Barline AddBarline(MeasureElement left, MeasureElement right)
        {
            var barline = new Barline(PackagePart, this, left, right);
            barlinesCollection.Add(barline);

            return barline;
        }

        /// <summary>
        /// Attempts to handle an incoming border request.
        /// </summary>
        /// <param name="request">
        /// <para>
        /// Type: <see cref="Internal.SystemBorderRequest"/>
        /// </para>
        /// <para>
        /// The request data.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="Boolean"/>
        /// </para>
        /// <para>
        /// <see langword="true"/> if the system handled the request, <see langword="false"/> otherwise.
        /// </para>
        /// </returns>
        /// <exception cref="InvalidOperationException">
        /// The <paramref name="request"/> is <see langword="null"/>.<br/>
        /// -or-<br/>
        /// The request is not recognized.<br/>
        /// -or-<br/>
        /// The request data are not valid.
        /// </exception>
        internal bool HandleBorderRequest(Internal.SystemBorderRequest request)
        {
            if (request.Request == Internal.BorderRequest.PROCESS_INNER_REQUEST)
            {
                var staff = staves.FirstOrDefault(s => s.Descriptor == request.InnerRequest.Descriptor);
                if (staff != null)
                {
                    staff.HandleBorderRequest(request.InnerRequest);
                    return true;
                }

                return false;
            }

            return false;
        }

        /// <summary>
        /// Processes an incoming staff border request.
        /// </summary>
        /// <remarks>
        /// The request is either passed to a <see cref="Staff.HandleBorderRequest"/> method
        /// if the logical neighbour is found or packed inside a <see cref="Internal.SystemBorderRequest"/>
        /// and passed to the <see cref="SectionInteriorElement.ProcessBorderRequest"/> method
        /// of the <see cref="ModelElement.Owner"/>.
        /// </remarks>
        /// <param name="request">
        /// <para>
        /// Type: <see cref="Internal.StaffBorderRequest"/>
        /// </para>
        /// <para>
        /// The request data.
        /// </para>
        /// </param>
        internal void ProcessBorderRequest(Internal.StaffBorderRequest request)
        {
            var outRequest = new Internal.SystemBorderRequest(Internal.BorderRequest.PROCESS_INNER_REQUEST, request.RequestDirection, this)
            {
                InnerRequest = request
            };
            
            FindOwner<MusicSectionInterior>().ProcessBorderRequest(outRequest);
        }

        /// <summary>
        /// Sets the range of measures presented in this system.
        /// </summary>
        /// <param name="firstMeasure">
        /// <para>
        /// Type: <see cref="MeasureElement"/>
        /// </para>
        /// <para>
        /// The first presented measure.
        /// </para>
        /// </param>
        /// <param name="lastMeasure">
        /// <para>
        /// Type: <see cref="MeasureElement"/>
        /// </para>
        /// <para>
        /// The last presented measure.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="firstMeasure"/> or <paramref name="lastMeasure"/> is <see langword="null"/>.
        /// </exception>
        internal void SetMeasures(MeasureElement firstMeasure, MeasureElement lastMeasure)
        {
            if (firstMeasure == null)
                throw new ArgumentNullException("firstMeasure");
            if (lastMeasure == null)
                throw new ArgumentNullException("lastMeasure");

            if (measuresCollection.Count > 0)
            {
                if (firstMeasure.Number > measuresCollection[0].Number)
                {
                    for (int i = Math.Min(firstMeasure.Number - measuresCollection[0].Number - 1, measuresCollection.Count - 1); i >= 0; --i)
                    {
                        measuresCollection.RemoveAt(i);                      
                        barlinesCollection.RemoveAt(i);

                        var right = barlinesCollection[i].Right;
                        barlinesCollection.RemoveAt(i);
                        barlinesCollection.Insert(i, new Barline(PackagePart, this, null, right));
                    }
                }

                if (measuresCollection.Count > 0 && lastMeasure.Number < measuresCollection[measuresCollection.Count - 1].Number)
                {
                    for (int i = measuresCollection.Count - 1; i >= Math.Max(measuresCollection.Count - measuresCollection[measuresCollection.Count - 1].Number + lastMeasure.Number, 0); --i)
                    {
                        measuresCollection.RemoveAt(i);
                        barlinesCollection.RemoveAt(i + 1);
                        
                        var left = barlinesCollection[i].Left;
                        barlinesCollection.RemoveAt(i);
                        barlinesCollection.Insert(i, new Barline(PackagePart, this, left, null));
                    }
                }
            }

            var composition = FindOwner<MusicSectionElement>().Composition.Composition;

            if (measuresCollection.Count == 0)
            {
                MeasureElement left = null;

                for (ushort i = firstMeasure.Number; i <= lastMeasure.Number; ++i)
                {
                    var measure = composition[i];
                    measuresCollection.Add(measure);
                    barlinesCollection.Add(new Barline(PackagePart, this, left, measure));
                    left = measure;
                }

                barlinesCollection.Add(new Barline(PackagePart, this, left, null));
            }
            else
            {
                if (firstMeasure.Number < measuresCollection[0].Number)
                {
                    int index = 0;
                    ushort actualNumber = firstMeasure.Number;
                    ushort stop = measuresCollection[0].Number;
                    MeasureElement left = null;

                    for (; actualNumber < stop; ++actualNumber, ++index)
                    {
                        var measure = composition[actualNumber];
                        measuresCollection.Insert(index, measure);
                        barlinesCollection.Insert(index, new Barline(PackagePart, this, left, measure));
                        left = measure;                        
                    }

                    barlinesCollection.RemoveAt(index);
                    barlinesCollection.Insert(index, new Barline(PackagePart, this, left, measuresCollection[index]));
                }

                if (lastMeasure.Number > measuresCollection[measuresCollection.Count - 1].Number)
                {
                    int index = measuresCollection.Count;
                    ushort actualNumber = (ushort)(measuresCollection[measuresCollection.Count - 1].Number + 1);
                    MeasureElement left = measuresCollection[measuresCollection.Count - 1];
                    barlinesCollection.RemoveAt(barlinesCollection.Count - 1);

                    for (; actualNumber <= lastMeasure.Number; ++actualNumber, ++index)
                    {
                        var measure = composition[actualNumber];
                        measuresCollection.Add(measure);
                        barlinesCollection.Add(new Barline(PackagePart, this, left, measure));
                        left = measure;
                    }

                    barlinesCollection.Add(new Barline(PackagePart, this, left, null));
                }
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Restores a previously created connector.
        /// </summary>
        /// <param name="connector">
        /// <para>
        /// Type: <see cref="Connector"/>
        /// </para>
        /// <para>
        /// The restored connector.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="connector"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The <paramref name="connector"/> does not belong to this system.<br/>
        /// -or-<br/>
        /// The system does not contain any connector that could be replaced by <paramref name="connector"/>.
        /// </exception>
        public void RestoreConnector(Connector connector)
        {
            if (connector == null)
                throw new ArgumentNullException("connector");
            if (connector.FindOwner<System>() != this)
                throw new ArgumentException("The restored connector does not belong to this system.", "connector");

            var replaced = connectorsCollection.FindIndex(c => c.PresentedConnector == connector.PresentedConnector);
            if (replaced == -1)
                throw new ArgumentException("The system does not contain any connector that could be replaced by the restored one.", "connector");
            connectorsCollection[replaced] = connector;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the collection of barlines in this system.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ReadOnlyCollection{T}"/> of <see cref="Barline"/>
        /// </para>
        /// <para>
        /// The collection of barlines in this system. The barlines separate measures
        /// and the collection is ordered from the leftmost barline to the rightmost one.
        /// </para>
        /// </value>
        public ReadOnlyCollection<Barline> Barlines
        {
            get { return barlines; }
        }

        /// <summary>
        /// Gets the collection of connectors presented by this system.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ReadOnlyCollection{T}"/> of <see cref="Connector"/>
        /// </para>
        /// <para>
        /// The collection of connectors presented by this system.
        /// </para>
        /// </value>
        public ReadOnlyCollection<Connector> Connectors
        {
            get { return connectors; }
        }

        /// <summary>
        /// Gets the collection of measures in this system.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ReadOnlyCollection{T}"/> of <see cref="MeasureElement"/>
        /// </para>
        /// <para>
        /// The collection of measures in this system.
        /// </para>
        /// </value>
        public ReadOnlyCollection<MeasureElement> Measures
        {
            get { return measures; }
        }

        /// <summary>
        /// Gets the collection of the staff connectors.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ElementCollection{T}"/> of <see cref="StaffConnector"/>
        /// </para>
        /// <para>
        /// The collection of staff connectors.
        /// </para>
        /// </value>
        public ElementCollection<StaffConnector> StaffConnectors
        {
            get { return staffConnectorsCollection; }
        }

        /// <summary>
        /// Gets the collection of staves in this system.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ElementCollection{T}"/> of <see cref="Staff"/>
        /// </para>
        /// <para>
        /// An ordered collection of staves in this system. 
        /// The staves are ordered from the topmost stave towards the one at the bottom.
        /// </para>
        /// </value>
        public ReadOnlyCollection<Staff> Staves
        {
            get { return staves; }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the value of the <see cref="Barlines"/> property.
        /// </summary>
        private ReadOnlyCollection<Barline> barlines;
        /// <summary>
        /// Holds the collection of barlines in this system.
        /// </summary>
        private ElementCollection<Barline> barlinesCollection;
        /// <summary>
        /// Holds the XML element that holds the <see cref="barlinesCollection"/> collection.
        /// </summary>
        private XmlElement barlinesElement;
        /// <summary>
        /// Holds the value of the <see cref="Connectors"/> property.
        /// </summary>
        private ReadOnlyCollection<Connector> connectors;
        /// <summary>
        /// Holds the collection of connectors.
        /// </summary>
        private ElementCollection<Connector> connectorsCollection;
        /// <summary>
        /// Holds the XML element that holds the <see cref="connectorsCollection"/>.
        /// </summary>
        private XmlElement connectorsElement;
        /// <summary>
        /// Holds the collection of measures in this system.
        /// </summary>
        private ObservableCollection<MeasureElement> measuresCollection;
        /// <summary>
        /// Holds the value of the <see cref="Measures"/> property.
        /// </summary>
        private ReadOnlyCollection<MeasureElement> measures;
        /// <summary>
        /// Holds the positionable helper.
        /// </summary>
        private PositionableHelper positionableHelper;
        /// <summary>
        /// Holds the resizable helper.
        /// </summary>
        private ResizableHelper resizableHelper;
        /// <summary>
        /// Holds the value of the <see cref="StaffConnectors"/> property.
        /// </summary>
        private ElementCollection<StaffConnector> staffConnectorsCollection;
        /// <summary>
        /// Holds the XML element that holds the <see cref="staffConnectorsCollection"/>.
        /// </summary>
        private XmlElement staffConnectorsElement;
        /// <summary>
        /// Holds the value of the <see cref="Staves"/> properties.
        /// </summary>
        private ReadOnlyCollection<Staff> staves;
        /// <summary>
        /// Holds the collection of staves in this system.
        /// </summary>
        private ElementCollection<Staff> stavesCollection;
        /// <summary>
        /// Holds the XML element that holds the <see cref="stavesCollection"/> collection.
        /// </summary>
        private XmlElement stavesElement;

        #endregion
    }
}
